//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

import SwiftDiagnostics
import SwiftParser
public import SwiftSyntax
public import SwiftSyntaxMacros

/// A type describing the expansion of the `@__testing` attribute macro.
///
/// Supported uses:
///
/// - `@__testing(semantics: "nomacrowarnings")`: suppress warning diagnostics
///   generated by macros. (The implementation of this use case is held in trust
///   at ``MacroExpansionContext/areWarningsSuppressed``.)
/// - `@__testing(warning: "...")`: emits `"..."` as a diagnostic message
///   attributed to the node to which the attribute is attached.
///
/// This type is used to implement the `@__testing` attribute macro. Do not use
/// it directly.
public struct PragmaMacro: PeerMacro, Sendable {
  public static func expansion(
    of node: AttributeSyntax,
    providingPeersOf declaration: some DeclSyntaxProtocol,
    in context: some MacroExpansionContext
  ) throws -> [DeclSyntax] {
    if case let .argumentList(arguments) = node.arguments,
       arguments.first?.label?.textWithoutBackticks == "warning" {
      let targetNode = Syntax(declaration)
      let messages = arguments
        .map(\.expression)
        .compactMap { $0.as(StringLiteralExprSyntax.self) }
        .compactMap(\.representedLiteralValue)
        .map { DiagnosticMessage(syntax: targetNode, message: $0, severity: .warning) }
      context.diagnose(messages)
    }

    return []
  }

  public static var formatMode: FormatMode {
    .disabled
  }
}

/// Get all pragma attributes (`@__testing`) associated with a syntax node.
///
/// - Parameters:
///   - node: The syntax node to inspect.
///
/// - Returns: The set of pragma attributes strings associated with `node`.
///
/// Attributes conditionally applied with `#if` are ignored.
func pragmas(on node: some WithAttributesSyntax) -> [AttributeSyntax] {
  node.attributes
    .compactMap { attribute in
      if case let .attribute(attribute) = attribute {
        return attribute
      }
      return nil
    }.filter { attribute in
      attribute.attributeName.isNamed("__testing", inModuleNamed: "Testing")
    }
}

/// Get all "semantics" attributed to a syntax node using the
/// `@__testing(semantics:)` attribute.
///
/// - Parameters:
///   - node: The syntax node to inspect.
///
/// - Returns: The set of "semantics" strings associated with `node`.
///
/// Attributes conditionally applied with `#if` are ignored.
func semantics(of node: some WithAttributesSyntax) -> [String] {
  pragmas(on: node)
    .compactMap { attribute in
      if case let .argumentList(arguments) = attribute.arguments {
        return arguments
      }
      return nil
    }.filter { arguments in
      arguments.first?.label?.textWithoutBackticks == "semantics"
    }.flatMap { argument in
      argument.compactMap { $0.expression.as(StringLiteralExprSyntax.self)?.representedLiteralValue }
    }
}
